# coding:utf-8

import os
import zipfile

from base_tool import str_time_list,WK_PATH,extract_zip_to_dir,shell
from flask import current_app

from config import Config,Caddy_config
CONFIG = Config()

from queue import Queue
from threading import Thread
from time import sleep

# 准备辅助线程
Q_01 = Queue()
F_01_status = {
    "msg":"",
    "log":[]
}
def F_01(Q=None,N=None):
    """
    辅助线程，用于执行后台任务,
    主线程将目标函数以及所需参数推入队列，由此函数来执行。
    注意：子线程内执行的函数将脱离 falsk 上下文，所执行函数需要解耦出来。
    @Q <- Queue 工作队列
        item = {
            "target":funcion,
            "args":list,
            "kwargs":dict
        }
    @N <- Dict 存储工作状态
    """
    while 1:
        if Q.empty():
            N["msg"]="子进程正在等待"
            sleep(3)
        else:
            t = str_time_list()
            t = "[{}-{}-{} {}:{}:{}] ".format(t[0],t[1],t[2],t[3],t[4],t[5])
            mission = Q.get()
            target = mission.get("target")
            if not target:
                continue
            if not isinstance(target,object):
                continue
            
            args = mission.get("args")
            kwargs = mission.get("kwargs")

            N["msg"]="子进程即将处理 {}, args:{}, kwargs:{}".format(str(target),str(args),str(kwargs))
            
            try:
                N["log"].append("{} 开始执行 {}".format(t,target.__name__))
                target(*args,**kwargs)
                N["log"].append("{} {} 执行完成".format(t,target.__name__))

            except Exception as e:
                N["log"].append("{} {} 执行失败, 原因是：{}".format(t,str(target),str(e)))


T = Thread(target=F_01,kwargs={"Q":Q_01,"N":F_01_status},daemon=True)
T.start()

class App(object):

    """
    部署app
    """

    @classmethod
    def new(cls,app_id=None):
        """
        部署一个应用
        @app_id String
        """
        
        a = CONFIG.config_cache["apps"].get(app_id)

        app_name = a.get("app_name")
        app_work_path = a.get("app_work_path")
        if os.path.isdir(app_work_path): 
            Log.write("已存在同名应用包，请更换名称或删除同名应用",app_id=app_id)
            return False

        os.makedirs(app_work_path)

        Log.write("{} 将被安装到 {}".format(app_name,app_work_path),app_id=app_id)

        # 校验应用压缩包
        Log.write("开始测试 {} 提供的应用包".format(app_name),app_id=app_id)

        with zipfile.ZipFile(os.path.join(WK_PATH,"archive","app_package",app_id), "r") as zip_ref:
            wsgi_exist = False
            pip_require_exist = False
            for i in zip_ref.filelist:
                if i.filename == "wsgi.py":
                    wsgi_exist = True
                elif i.filename == "requirements.txt":
                    pip_require_exist = True
            if not wsgi_exist:
                Log.write("应用包根目录内未发现入口文件 wsgi.py，请再次检查",app_id=app_id)
                return False
            elif not pip_require_exist:
                Log.write("应用包根目录内未发现依赖描述文件 requirements.txt，请再次检查",app_id=app_id)
                return False


        # 创建应用目录并解压应用包
        Log.write("开始解压 {} 提供的应用包".format(app_name),app_id=app_id)
        t = extract_zip_to_dir(zip_file_path=os.path.join(WK_PATH,"archive","app_package",app_id), target_dir_path=app_work_path)
        if not t:
            Log.write("应用包在解压过程中出现错误",app_id=app_id)
            return False

        # 为应用安装虚拟环境和依赖
        Log.write("开始为 {} 安装虚拟环境".format(app_name),app_id=app_id)
        venv_path = os.path.join(app_work_path, "venv")
        os.makedirs(venv_path)

        r = shell("python3.7 -m venv {}".format(venv_path))
        Log.write(r[1].split("\n"),app_id=app_id)
        
        Log.write("开始为 {} 安装依赖".format(app_name),app_id=app_id)
        pip_bin_path = os.path.join(app_work_path, "venv", "bin", "pip3.7")
    
        r = shell("{} install -r {} -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn && {} install gunicorn -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn".format(
            pip_bin_path, 
            os.path.join(app_work_path, "requirements.txt"),
            pip_bin_path
        ))
        Log.write(r[1].split("\n"),app_id=app_id)
        # 开始创建守护文件
        Daemon.build(app_id=app_id)
        # 更新配置信息
        CONFIG.update_app(app_id=app_id,app_status=1)

        if a.get("app_mode") == "1":
            shell("systemctl restart caddy_bin")

        return True

    @staticmethod        
    def delete_app(app_id=None):
        try:
            # 停止服务，移除服务，移除应用文件，如果配置了域名，还需要重载 web 服务器。
            os.system("systemctl stop {}".format(app_id))
            os.system("systemctl disable {}".format(app_id))
            os.system("rm -rf {}".format(os.path.join("/etc/systemd/system/","{}.service".format(app_id))))
            os.system("rm -rf {}".format(os.path.join(WK_PATH,"apps",app_id)))
            CONFIG.delete_app(app_id=app_id)
            Caddy_config.rebuild()
            os.remove(os.path.join("/etc/systemd/system/","{}.service".format(app_id)))
        except Exception as e:
            Log.write(str(e))
        

    @staticmethod
    def start_app(app_id=None):
        """
        启动应用
        """
        r = shell("systemctl start {}".format(app_id))
        Log.write(r[1].split("\n"),app_id=app_id)

    @staticmethod
    def restart_app(app_id=None):
        """
        重启应用
        """
        r = shell("systemctl restart {}".format(app_id))
        Log.write(r[1].split("\n"),app_id=app_id)

    @staticmethod
    def stop_app(app_id=None):
        """
        停止应用
        """
        r = shell("systemctl stop {}".format(app_id))
        Log.write(r[1].split("\n"),app_id=app_id)

    @staticmethod       
    def disable_app(app_id=None):
        """
        关闭守护
        """
        r = shell("systemctl disable {}".format(app_id))
        Log.write(r[1].split("\n"),app_id=app_id)
            
    @staticmethod
    def enable_app(app_id=None):
        """
        启用守护
        """
        r = shell("systemctl enable {}".format(app_id))
        Log.write(r[1].split("\n"),app_id=app_id)
            
    @staticmethod
    def app_unit_status(app_id=None):
        """
        获取应用的状态
        """
        r = shell("systemctl status {}".format(app_id))
        return r[1].split("\n")

class Log(object):

    """
    提供一个简单的日志功能
    @line -> 单条日志内容
    @app_name -> 应用名称 -> 默认为 None
        默认情况下将在本程序运行目录下 log.txt 写入/读取/清除日志
        若提供此参数，则在 app_name 对应的应用运行目录中下 log.txt 写入/读取/清除日志
    
    内部的 log_path 判断应该可以抽象出来，考虑装饰器实现
    """

    log_path = os.path.join(WK_PATH,"log.txt")
    app_root_path = os.path.join(WK_PATH,"apps")


    @classmethod
    def write(cls,line,app_id=None):
        """
        写日志，默认追加写入
        @line String
        @app_name String
        """
        if bool(app_id):
            log_path = os.path.join(cls.app_root_path,app_id,"log.txt")
        else:
            log_path = cls.log_path

        with open(log_path, 'at', encoding="utf-8") as f:
            if not isinstance(line,list):
                try:
                    line = str(line)
                except:
                    line = "Error"
                line = [line]

            for l in line:
                t = str_time_list()
                f.write("[{}-{}-{} {}:{}:{}] ".format(t[0],t[1],t[2],t[3],t[4],t[5]))
                f.write(str(l))
                f.write("\n")

    @classmethod 
    def read(cls,app_id=None):
        """
        读日志
        @line String
        @app_name String
        """
        if bool(app_id):
            log_path = os.path.join(cls.app_root_path,app_id,"log.txt")
        else:
            log_path = cls.log_path

        if not os.path.isfile(log_path):return False
        with open(log_path, 'rt') as f:
            lines = []
            for line in f:
                lines.append(line.replace("\n",""))
            return lines

    @classmethod
    def clean(cls,app_id=None):
        """
        清除日志
        @app_name String
        """
        if bool(app_id):
            log_path = os.path.join(cls.app_root_path,app_id,"log.txt")
        else:
            log_path = cls.log_path
        with open(log_path, 'wt') as f:
            f.write("")


class Daemon(object):
    @staticmethod
    def build(app_id=None):
        Log.write("开始创建守护文件",app_id=app_id)
        
        daemon_template_path = os.path.join(WK_PATH, "config","daemon","app.service")
        daemon_path = "/etc/systemd/system/{}.service".format(app_id)
        if os.path.isfile(daemon_path):
            Log.write("守护文件已存在，将进行覆盖操作",app_id=app_id)


        with open(daemon_template_path, "rt", encoding="utf-8") as t:
            t = t.read()
            app_info = CONFIG.config_cache["apps"].get(app_id)
            # app_run_host = app_info["app_host"]
            app_run_host = "0.0.0.0"
            t = t.format(app_work_path=app_info["app_work_path"],app_gunicorn_path=os.path.join(app_info["app_work_path"], "venv", "bin", "gunicorn"),app_run_workers=app_info["app_run_workers"],app_run_host=app_run_host,app_run_port=app_info["app_run_port"])
            with open(daemon_path, "wt", encoding="utf-8") as d:
                d.write(t)
                Log.write("守护文件写入完成",app_id=app_id)

        return True
            
    @staticmethod
    def delete(app_id=None):
        pass
    
class Caddyfile(object):
    @staticmethod
    def build(app_list=None):
        """
        读取配置,重建 虚拟主机配置文件
        """
        pass


# if __name__ == "__main__":
#     Log.write("aaaaaaaaaaassss")
#     Log.write(['a','b','c'])
#     print(Log.read())
#     from base_tool import timer,to_md5,timestamp,timestamp_13
#     @timer
#     def test():
#         a = []
#         for i in range(100000):
#             a.append([to_md5(raw=timestamp_13(),mix_text=i),timestamp()])
           
#         Log.write(a)
#     test()